<?php

namespace app\service;

use think\Cache;
use think\Exception;

class RateLimiterService
{
    /**
     * 令牌桶算法限流
     * @param string $key 限流器标识（如IP、用户ID）
     * @param int $capacity 桶容量（最大令牌数）
     * @param float $rate 令牌生成速率（每秒生成多少个令牌）
     * @return bool 是否允许请求
     */
    public static function allow($key,$capacity,$rate)
    {
        $redis = Cache::store('redis')->handler();
        $now = microtime(true); // 当前时间戳（微秒级）

        // 限流器Redis键
        $tokenKey = "rate_limit:{$key}:tokens";
        $timestampKey = "rate_limit:{$key}:timestamp";

        // 获取当前令牌数和上次更新时间
        $currentTokens = $redis->get($tokenKey);
        $lastTime = $redis->get($timestampKey);

        // 初始化值
        if($currentTokens === false || $lastTime === false){
            $currentTokens = $capacity;
            $lastTime = $now;
        }else{
            $currentTokens = (float)$currentTokens;
            $lastTime = (float)$lastTime;
        }

        // 计算时间差并生成新令牌
        $delta = $now - $lastTime; // 时间差（微秒）
        $newTokens = $delta * $rate; // 新生成的令牌数

        // 更新令牌数（不超过桶容量）
        $currentTokens = min($capacity, $currentTokens + $newTokens);

        // 如果令牌数小于1，则拒绝请求
        if ($currentTokens < 1) {
            return false;
        }

        // 消耗一个令牌
        $currentTokens -= 1;

        // 更新Redis
        $redis->set($tokenKey, $currentTokens);
        $redis->set($timestampKey, $now);

        return true;
    }

    /**
     * 滑动窗口限流（基于Redis有序集合）
     * @param string $key 限流器标识
     * @param int $windowSize 窗口大小（秒）
     * @param int $maxRequests 窗口内最大请求数
     * @return bool 是否允许请求
     */
    public function slidingWindow($key, $windowSize, $maxRequests)
    {
        $redis = Cache::store('redis')->handler();
        $now = microtime(true) * 1000; // 毫秒时间戳

        // 限流器Redis键
        $windowKey = "rate_limit:{$key}:window";

        // 移除时间窗口外的请求
        $redis->zRemRangeByScore($windowKey, 0, $now - $windowSize * 1000);

        // 获取当前窗口内的请求数
        $requestCount = $redis->zCard($windowKey);

        // 如果请求数超过限制
        if ($requestCount >= $maxRequests) {
            return false;
        }

        // 添加当前请求到窗口
        $redis->zAdd($windowKey, $now, $now);

        // 设置过期时间（避免数据永久存储）
        $redis->expire($windowKey, $windowSize + 1);

        return true;
    }

    /**
     * 固定窗口限流（简单计数）
     * @param string $key 限流器标识
     * @param int $interval 时间窗口（秒）
     * @param int $maxRequests 最大请求数
     * @return bool 是否允许请求
     */
    public function fixedWindow($key, $interval, $maxRequests)
    {
        $redis = Cache::store('redis')->handler();

        // 限流器Redis键（基于当前时间窗口）
        $window = floor(time() / $interval);
        $windowKey = "rate_limit:{$key}:{$window}";

        // 获取当前计数
        $count = $redis->get($windowKey) ?: 0;

        // 如果计数超过限制
        if ($count >= $maxRequests) {
            return false;
        }

        // 增加计数
        $redis->incr($windowKey);

        // 设置过期时间（等于时间窗口）
        $redis->expire($windowKey, $interval);

        return true;
    }
}